package store.lunangangster.engine.impl.repository;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.NodeFlowObjectNotFoundException;
import store.lunangangster.engine.ProcessEngineConfiguration;
import store.lunangangster.engine.impl.entity.DeploymentEntity;
import store.lunangangster.engine.impl.entity.ProcessDefinitionEntity;
import store.lunangangster.engine.impl.entity.ResourceEntity;
import store.lunangangster.engine.impl.entity.mgr.ProcessDefinitionEntityManager;

/**
 * 用于解析Deployment为Bpmn模型以及其他引擎需要的对象，并即将用于部署。
 *
 * <p>如果{@link DeploymentEntity#isNew()} == true，则会将其持久化到DB，并更新到缓存；
 * <p>否则只会将其更新到缓存中。
 *
 * @author cwk
 * @version 1.0
 */
public class BpmnDeployer {

  protected ProcessEngineConfiguration processEngineConfiguration;
  protected DeploymentParseBuilderFactory deploymentParseBuilderFactory;
  protected ProcessDefinitionEntityManager processDefinitionEntityManager;
  protected DeployCacheManager deployCacheManager;

  public BpmnDeployer(ProcessEngineConfiguration processEngineConfiguration) {
    this.processEngineConfiguration = processEngineConfiguration;
    this.deploymentParseBuilderFactory = processEngineConfiguration.getDeploymentParseBuilderFactory();
    this.processDefinitionEntityManager = processEngineConfiguration.getProcessDefinitionEntityManager();
    this.deployCacheManager = processEngineConfiguration.getDeployCacheManager();
  }

  public void deploy(DeploymentEntity deployment) {
    // get Deployment's Resources and parse them to DeploymentParse object
    // DeploymentParse is an Object that contains lists of ProcessDefinition, and
    // keep the BpmnParse map to ProcessDefinition
    DeploymentParse deploymentParse = deploymentParseBuilderFactory
        .createBuilderForDeployment(deployment)
        .build();
    List<ProcessDefinitionEntity> parsedProcessDefinitions = deploymentParse.getProcessDefinitionEntities();

    checkProcessDefinitionShareNoKeys(parsedProcessDefinitions);
    copyDeploymentValueToProcessDefinitions(deployment, parsedProcessDefinitions);
    setResourceNameToProcessDefinitions(deploymentParse.getProcessDefinitionToResourceMap());
    // if new, then need to persist
    if (deployment.isNew()) {
      Map<ProcessDefinitionEntity, ProcessDefinitionEntity> newToPreviousProcessDefinitionMap
          = getPreviousVersionProcessDefinitionHavingDeployed(parsedProcessDefinitions);
      setNextVersionToNewProcessDefinition(newToPreviousProcessDefinitionMap);
      // TODO 部署了新的ProcessDefinition后，对先前正在参与运行的旧版本ProcessDefinition做处理
      // persist ProcessDefinition
      persistProcessDefinitionEntity(deploymentParse);
    } else {
      // else, search from DB and get persisted ProcessDefinition to set relative value
      setPersistProcessDefinitionValueToThese(parsedProcessDefinitions);
    }
    // cache
    deployCacheManager.updateCache(deploymentParse);
  }

  /**
   * 从DB中查找出已经持久化的ProcessDefinition，并且将其一些持久化属性注册到传入的ProcessDefinitions中
   */
  private void setPersistProcessDefinitionValueToThese(List<ProcessDefinitionEntity> parsedProcessDefinitions) {
    for (ProcessDefinitionEntity parsedProcessDefinition : parsedProcessDefinitions) {
      ProcessDefinitionEntity processDefinitionEntity = processDefinitionEntityManager.selectLatestProcessDefinitionByKey(
          parsedProcessDefinition.getKey());
      if (processDefinitionEntity != null) {
        parsedProcessDefinition.setId(processDefinitionEntity.getId());
        parsedProcessDefinition.setVersion(processDefinitionEntity.getVersion());
      } else {
        throw new NodeFlowObjectNotFoundException("找不到Key为" + parsedProcessDefinition.getKey() + "的ProcessDefinition定义");
      }
    }

  }

  protected void checkProcessDefinitionShareNoKeys(
      List<ProcessDefinitionEntity> processDefinitionEntities) {
    Set<String> hashset = new HashSet<>();
    for (ProcessDefinitionEntity processDefinitionEntity : processDefinitionEntities) {
      if (hashset.contains(processDefinitionEntity.getKey())) {
        throw new NodeFlowException("在同一次部署中存在key为【" + processDefinitionEntity.getKey() + "】的两个相同的Process定义");
      }
      hashset.add(processDefinitionEntity.getKey());
    }
  }

  protected void copyDeploymentValueToProcessDefinitions(DeploymentEntity deployment,
      List<ProcessDefinitionEntity> processDefinitionEntities) {
    processDefinitionEntities.forEach(processDefinitionEntity -> processDefinitionEntity.setDeploymentId(deployment.getId()));
  }

  protected void setResourceNameToProcessDefinitions(Map<ProcessDefinitionEntity, ResourceEntity> processDefinitionToResourceMap) {
    for (ProcessDefinitionEntity processDefinitionEntity : processDefinitionToResourceMap.keySet()) {
      ResourceEntity resource = processDefinitionToResourceMap.get(processDefinitionEntity);
      processDefinitionEntity.setResourceName(resource.getName());
    }
  }

  protected Map<ProcessDefinitionEntity, ProcessDefinitionEntity> getPreviousVersionProcessDefinitionHavingDeployed(
      List<ProcessDefinitionEntity> processDefinitionEntities) {
    Map<ProcessDefinitionEntity, ProcessDefinitionEntity> processDefinitionMap = new LinkedHashMap<>();
    for (ProcessDefinitionEntity newProcessDefinition : processDefinitionEntities) {
      ProcessDefinitionEntity preVersionProcessDefinition = processDefinitionEntityManager.selectLatestProcessDefinitionByKey(
          newProcessDefinition.getKey());
      processDefinitionMap.put(newProcessDefinition, preVersionProcessDefinition);
    }
    return processDefinitionMap;
  }

  protected void setNextVersionToNewProcessDefinition(
      Map<ProcessDefinitionEntity, ProcessDefinitionEntity> newToPreviousProcessDefinitionMap) {
    for (ProcessDefinitionEntity newProcessDefinition : newToPreviousProcessDefinitionMap.keySet()) {
      ProcessDefinitionEntity previousProcessDefinition = newToPreviousProcessDefinitionMap.get(
          newProcessDefinition);
      // increase version
      int newVersion = previousProcessDefinition == null ? 1 : previousProcessDefinition.getVersion() + 1;
      newProcessDefinition.setVersion(newVersion);
    }
  }

  protected void persistProcessDefinitionEntity(DeploymentParse deploymentParse) {
    for (ProcessDefinitionEntity processDefinition : deploymentParse.getProcessDefinitionEntities()) {
      processDefinitionEntityManager.insert(processDefinition);
    }
  }
}
